Skip to content

Comments

Add automatic browser-based OAuth flow#6

Merged
justinpecott merged 11 commits intomainfrom
auto-browser-oauth
Oct 15, 2025
Merged

Add automatic browser-based OAuth flow#6
justinpecott merged 11 commits intomainfrom
auto-browser-oauth

Conversation

@justinpecott
Copy link
Owner

Summary

Implements a seamless OAuth authentication experience that eliminates manual URL copy/pasting:

  • Automatically opens the browser to the authorization URL
  • Spins up a local HTTPS server to capture the OAuth callback
  • Generates self-signed SSL certificates for localhost
  • Falls back gracefully to manual flow if automatic flow fails
  • Shows a success page in the browser after authorization

Benefits

  • Better UX: Users just click "Authorize" in the browser - no copy/paste needed
  • Fewer errors: Eliminates issues from malformed URLs or copy/paste mistakes
  • Industry standard: Follows patterns used by popular CLI tools (gcloud, aws-cli, gh)
  • Graceful fallback: If automatic flow fails, users can still complete auth manually

Technical Details

  • Uses Python's built-in http.server and threading modules
  • Generates self-signed certificates via OpenSSL
  • 2-minute timeout for authorization with automatic fallback
  • SSL certificates are cached in .api-blaster/ directory

Test Plan

  • Test first-time OAuth setup with automatic browser flow
  • Test manual fallback when automatic flow fails
  • Test on different browsers and operating systems
  • Verify SSL certificate generation works correctly
  • Test with both HTTP and HTTPS redirect URIs

🤖 Generated with Claude Code

Implements a seamless OAuth authentication experience:
- Auto-opens browser to authorization URL
- Spins up local HTTPS server to capture OAuth callback
- Generates self-signed SSL certificate for localhost
- Falls back to manual copy/paste flow if automatic fails
- Shows success page in browser after authorization

This eliminates the need for users to manually copy/paste URLs,
making the initial setup much smoother. The implementation follows
patterns used by popular CLI tools like gcloud, aws-cli, and gh.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Oct 15, 2025

Pull Request Review: Add automatic browser-based OAuth flow

This is a solid enhancement that significantly improves UX. However, there are several important issues to address:

Critical Issues to Fix

1. URL Construction Bug (line 238)
The redirect URL construction creates invalid URLs. If redirect_uri is https://localhost:9090/cb and self.path is /?code=ABC, you get https://localhost:9090/cb/?code=ABC (double slash).

Fix: Use urlparse to construct properly.

2. Race Condition (lines 226-229)
Browser opens after server starts, creating a race condition. The callback might arrive before server is ready.

Fix: Use threading.Event to signal server is ready before opening browser.

3. Server Resource Leak (lines 169-199)
HTTPServer is never cleaned up. Socket binding persists and could cause 'Address already in use' errors.

Fix: Add server.server_close() in finally block.

4. Unused Imports (lines 8, 144-145)
parse_qs, tempfile, and os are imported but never used.

Security Concerns

5. No State Validation (line 17-20)
OAuth callback doesn't validate state parameter against CSRF attacks. OAuth2Session generates state (line 213) but it's never validated.

6. Thread Safety Issue (line 15)
Class variable authorization_response is not thread-safe. Multiple concurrent OAuth flows will overwrite each other.

Testing Gaps

7. No Test Coverage
PR includes no tests. Need tests for: successful flow, fallback scenarios, HTTP vs HTTPS, timeout handling, port conflicts.

Positive Aspects

  • Excellent fallback to manual flow
  • Good use of stdlib (no extra dependencies)
  • Smart certificate caching
  • User-friendly success page
  • Clear documentation

Verdict

Request Changes - Great concept and valuable UX improvement, but issues #1, #2, #3, and #6 must be fixed before merging. Add basic test coverage.

justinpecott and others added 5 commits October 14, 2025 23:08
The previous implementation incorrectly concatenated the redirect URI
with the callback path, resulting in malformed URLs like:
  https://localhost:9090/cb/?code=ABC (double slash before query)

Now properly uses urlparse/urlunparse to construct valid URLs:
- Extracts scheme and netloc from configured redirect_uri
- Extracts path and query from callback response
- Combines them correctly without double slashes

Example:
  redirect_uri: https://localhost:9090/cb
  callback: /?code=ABC&state=XYZ
  result: https://localhost:9090/cb?code=ABC&state=XYZ

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The HTTPServer socket was never properly closed, causing resource leaks
that could result in "Address already in use" errors on subsequent runs
or if the script crashes during authentication.

Changes:
- Wrapped server operations in try/finally block
- Added server.server_close() in finally to ensure socket cleanup
- Socket is now properly released even if errors occur during auth flow

This prevents port binding issues and ensures clean resource management.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added threading.Event to ensure the callback server is fully ready
before opening the browser. This eliminates the theoretical race
condition where the OAuth redirect could arrive before the server
is listening.

Changes:
- _start_callback_server now returns tuple (response, server_ready_event)
- Server thread signals ready via event.set() before handling requests
- Browser only opens after confirming server is ready
- Added 5-second timeout for server startup with fallback to manual flow

While the race window was extremely small in practice (microseconds),
this makes the flow bulletproof and adds proper error handling for
server startup failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Cleaned up imports that were not actually used in the code:
- parse_qs from urllib.parse (line 8)
- tempfile and os from _generate_self_signed_cert (lines 144-145)

These were likely left over from earlier iterations of the code.
Keeping the codebase clean and removing dead imports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The class variable authorization_response was shared across all
CallbackHandler instances, causing race conditions when multiple
OAuth flows run concurrently. Multiple servers would overwrite
each other's responses.

Changes:
- Replaced class variable with queue.Queue for thread-safe communication
- Each server instance now gets its own queue
- Response is retrieved via queue.get() with timeout
- Proper isolation between concurrent OAuth flows

This enables safe concurrent authentication to multiple APIs without
responses getting mixed up or lost.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Oct 15, 2025

Pull Request Review: Add automatic browser-based OAuth flow

Overview

This PR adds an excellent UX improvement by implementing automatic browser-based OAuth flow with graceful fallback. The implementation follows industry-standard patterns used by popular CLI tools. Overall, this is a solid enhancement with good error handling and fallback mechanisms.

🟢 Strengths

  1. Excellent UX improvement - Eliminates error-prone manual copy/paste workflow
  2. Graceful degradation - Multiple fallback mechanisms if automatic flow fails
  3. Good separation of concerns - Well-organized into dedicated methods
  4. Certificate caching - Avoids regenerating SSL certs on every run
  5. Thread-safe queue usage - Proper use of queue.Queue for thread communication
  6. Clean HTML success page - Good user feedback after authorization

🟡 Issues & Recommendations

High Priority

1. Race Condition in CallbackHandler (oauth_session.py:176-177)

response_queue = queue.Queue()
CallbackHandler.response_queue = response_queue

Issue: Class variable response_queue is shared across all instances. If multiple OAuth flows run concurrently, they'll interfere with each other.

Fix: Create a custom handler class per-instance:

def _start_callback_server(self, host: str, port: int, use_https: bool = True):
    response_queue = queue.Queue()
    
    # Create handler class with bound queue
    class BoundCallbackHandler(BaseHTTPRequestHandler):
        def do_GET(self):
            response_queue.put(self.path)
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            success_html = """..."""
            self.wfile.write(success_html.encode())
        
        def log_message(self, format, *args):
            pass
    
    server = HTTPServer((host, port), BoundCallbackHandler)
    # ... rest of the method

2. Port Binding Failure Not Handled (oauth_session.py:180)

server = HTTPServer((host, port), CallbackHandler)

Issue: If the port is already in use (e.g., previous run didn't clean up), this raises an OSError and crashes instead of falling back to manual flow.

Fix: Wrap in try-except:

try:
    server = HTTPServer((host, port), CallbackHandler)
except OSError as e:
    print(f"Warning: Could not bind to port {port}: {e}, falling back to manual flow")
    return None, server_ready

3. Logic Error in Server Ready Check (oauth_session.py:250)

if redirect_response is None or not server_ready.is_set():

Issue: redirect_response is None initially, but server_ready may not be set yet. This causes immediate fallback even when server starts successfully.

Fix: Check logic should be:

# Start server first
redirect_response, server_ready = self._start_callback_server(host, port, use_https)

# Only open browser if server started successfully
if redirect_response is None and not server_ready.is_set():
    # Server failed to start
    print("\nAutomatic flow failed.")
    print(f"Please visit: {authorization_url}")
    redirect_response = input("Paste the full redirect URL here: ")
elif server_ready.is_set():
    # Server started, open browser and wait
    webbrowser.open(authorization_url)
    # redirect_response is already set or None from timeout

4. Missing SSL Certificate File Permissions

Issue: Generated certificate files (oauth_session.py:147-148) don't have restricted permissions. Private keys should not be world-readable.

Fix: After generating certificates:

import os
subprocess.run([...], check=True, capture_output=True)
os.chmod(key_file, 0o600)  # Owner read/write only
os.chmod(cert_file, 0o644)  # Owner read/write, others read
return str(cert_file), str(key_file)

Medium Priority

5. Python 3.9 Compatibility Issue (oauth_session.py:169)

def _start_callback_server(self, host: str, port: int, use_https: bool = True) -> tuple[Optional[str], threading.Event]:

Issue: Lowercase tuple[...] type hint requires Python 3.9+, but many systems still use 3.8.

Fix: Use Tuple from typing (already imported):

from typing import Tuple  # Add to imports
def _start_callback_server(self, host: str, port: int, use_https: bool = True) -> Tuple[Optional[str], threading.Event]:

6. Unclear Error Messages

Multiple "Automatic flow failed" messages (lines 252, 261) don't explain why it failed.

Fix: Return failure reason from _start_callback_server:

return None, server_ready, "Port in use"
# Then use it:
print(f"\nAutomatic flow failed: {reason}")

7. Subprocess Import at Method Level (oauth_session.py:145)

def _generate_self_signed_cert(self) -> tuple:
    import subprocess

Issue: Import should be at module level for better performance and clarity.

Fix: Add to top-level imports.

8. Browser Security Warnings

Issue: Self-signed certificates will cause browser security warnings, which might confuse users.

Recommendation: Add user messaging:

print("Opening browser for authorization...")
print("Note: You may see a security warning about the certificate - this is expected.")
print("Click 'Advanced' and 'Proceed to localhost' to continue.")

Low Priority

9. Type Hint Inconsistency (oauth_session.py:143)

def _generate_self_signed_cert(self) -> tuple:

Issue: Should be Tuple[Optional[str], Optional[str]] for consistency.

10. Hardcoded 2-minute Timeout

redirect_response = response_queue.get(timeout=120)

Recommendation: Make configurable via constructor parameter.

11. No Certificate Expiry Check

Generated certificates expire after 365 days (line 160). Old certificates should be regenerated.

Fix: Check file modification time and regenerate if > 365 days old.

🔒 Security Assessment

✅ Good Practices

  • Self-signed certificates for localhost HTTPS
  • Automatic credential refresh
  • Tokens stored locally (not logged)
  • Server only handles one request then closes

⚠️ Concerns

  1. Private key permissions (see issue Add Claude Code GitHub Workflow #4 above)
  2. Certificates stored with secrets - Consider warning users about .api-blaster/ directory security in README
  3. No CSRF protection - OAuth state parameter generated but not validated in callback (though this is less critical for localhost flows)

🧪 Testing Recommendations

The PR mentions a test plan but no automated tests are included. Suggest adding:

  1. Unit tests:

    • Certificate generation (mock subprocess)
    • URL parsing logic
    • Fallback scenarios
  2. Integration tests:

    • Mock OAuth server
    • Port conflict scenarios
    • Timeout scenarios
  3. Manual testing checklist (as mentioned in PR description):

    • ✅ First-time setup with automatic flow
    • ✅ Manual fallback when port unavailable
    • ✅ Different redirect URI schemes (http/https)
    • ✅ Browser doesn't open (test on headless systems)
    • ✅ User denies authorization

📈 Performance Considerations

  • Good: Certificate generation is cached
  • Good: Server uses single-request handler (no persistent connection overhead)
  • Minor: 5-second server startup timeout (line 207) could be reduced to 2 seconds

📝 Documentation Suggestions

  1. Add docstring note about browser security warnings
  2. Document that openssl CLI tool is required
  3. Update README with firewall/port requirements
  4. Add troubleshooting section for common issues

Summary

This is a well-designed feature that significantly improves UX. The main concerns are:

  1. Must fix: Race condition with shared class variable (Migrate OAuth configuration from Amazon to Cognito #1)
  2. Must fix: Port binding error handling (Encapsulate Oauth Logic for easier Script Reuse #2)
  3. Should fix: Logic error in server ready check (Encapsulate OAuth Logic for Easier Script Reuse #3)
  4. Should fix: Certificate file permissions (Add Claude Code GitHub Workflow #4)
  5. Should fix: Python 3.9 type hint compatibility (Add Agent Fight results from last month #5)

With these fixes, this would be an excellent addition to the codebase. Great work on the graceful fallback mechanisms!


🤖 Generated with Claude Code

justinpecott and others added 3 commits October 14, 2025 23:23
The previous fix still had a race condition: response_queue was a
class variable that could be overwritten if multiple OAuth flows
ran concurrently. The second flow would overwrite the first's queue,
causing responses to be routed to the wrong server.

Changes:
- Removed global CallbackHandler class
- Create BoundCallbackHandler dynamically per-server with queue in closure
- Queue is now truly isolated per server instance
- Each concurrent OAuth flow gets its own handler class with its own queue

This properly isolates concurrent OAuth flows using Python closures
instead of shared class variables.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
If the port is already in use (from crashed process, another app, etc),
HTTPServer() raises OSError and crashes the OAuth flow. This now catches
the error and falls back to manual URL copy/paste flow.

Changes:
- Wrapped HTTPServer creation in try/except for OSError
- Prints helpful error message with port and reason
- Returns None to trigger fallback to manual flow
- Prevents crash and provides better user experience

Common scenarios this fixes:
- Port still bound from previous crashed run
- Another process using the port
- Permissions issues binding to privileged ports

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The previous logic was broken:
  if redirect_response is None or not server_ready.is_set():

This would ALWAYS take the fallback path because redirect_response
is always None immediately after server starts (no callback yet).
Browser would never open in automatic mode.

Correct flow:
1. Check if server_ready is set (server started successfully)
2. If yes, open browser and wait for response
3. If no response after timeout, fall back to manual
4. If server didn't start, immediately fall back to manual

Changes:
- Fixed conditional logic to check server_ready first
- Only open browser if server started successfully
- Separate error messages for "server failed" vs "timeout"
- Clearer flow control with nested if/else

Now the automatic browser flow actually works!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Oct 15, 2025

Pull Request Review: Add automatic browser-based OAuth flow

Overview

This PR adds a significant UX improvement by automating the OAuth flow with browser-based authentication and a local callback server. The implementation follows patterns used by established CLI tools like gcloud and aws-cli.

✅ Strengths

  1. Excellent graceful degradation: The fallback to manual flow at multiple failure points is well-designed
  2. Good security awareness: Uses HTTPS by default with self-signed certificates
  3. Clean code organization: New methods are well-separated and focused
  4. Helpful user feedback: Clear messages guide users through each step

🔴 Critical Issues

1. Race Condition in Server Thread (Lines 200-219)

Severity: High

The current implementation has a potential race condition between setting server_ready and the server actually being ready to accept connections:

def run_server():
    server_ready.set()  # Signal that server is ready
    server.handle_request()  # Handle one request then stop

Issue: The event is set before the server enters its request handling loop. If the browser request arrives before handle_request() starts listening, it will fail.

Fix: Move the event signal to after the server is actually listening, or add a small delay.

2. Missing subprocess Import at Module Level (Line 114)

Severity: Medium

import subprocess is inside _generate_self_signed_cert() method. While this works, it's unconventional and can cause issues with static analysis tools and IDEs.

Fix: Move to top-level imports.

3. Incomplete Error Handling for Token Fetch (Lines 284-289)

Severity: Medium

The oauth_session.fetch_token() call can raise multiple exceptions (network errors, invalid responses, authorization denied) but has no try-except handling. Users will get raw exception tracebacks instead of helpful error messages.

Fix:

try:
    token = oauth_session.fetch_token(...)
except Exception as e:
    print(f"Failed to fetch access token: {e}")
    raise

⚠️ Security Concerns

1. Self-Signed Certificate Vulnerabilities

Severity: Medium

  • Certificates stored in .api-blaster/ are generated once and reused (lines 119-121)
  • 365-day validity is excessive for a development tool
  • No validation of certificate age or integrity
  • Private keys stored unencrypted on disk

Recommendations:

  • Regenerate certificates more frequently (e.g., 30 days)
  • Add certificate expiry checking
  • Set restrictive file permissions on private key (chmod 600)
  • Consider using Python's cryptography library instead of shelling out to OpenSSL

2. OpenSSL Command Injection Risk

Severity: Low

Lines 125-131: While config_dir is a Path object (safe), if user input could ever influence this path, the subprocess call could be vulnerable. Currently safe but worth noting.

3. HTTPS Not Validated

Severity: Info

The self-signed certificate approach means browsers will show security warnings. This is acceptable for localhost OAuth but should be documented for users.

🐛 Potential Bugs

1. Port Binding Race Condition

Lines 240-241

Default port 9090 may already be in use. While there's error handling (line 182-185), the user experience could be improved by trying multiple ports.

Suggestion: Try ports 9090-9099 in sequence before falling back to manual flow.

2. Webbrowser.open() Failure Not Handled

Line 259

webbrowser.open() returns False if no browser is found, but this return value is ignored. Users might wait indefinitely for a browser that never opens.

Fix:

if not webbrowser.open(authorization_url):
    print("Warning: Could not open browser automatically")

3. URL Parsing Edge Cases

Lines 269-281

The URL reconstruction logic doesn't handle:

  • Custom ports in callbacks
  • URL fragments (should be preserved)
  • URL-encoded characters in query strings

Test case needed: Verify behavior when OAuth provider returns error parameters in callback.

📊 Code Quality Issues

1. Type Hint Syntax Compatibility

Line 138

Uses tuple[Optional[str], threading.Event] (PEP 604 syntax), which requires Python 3.10+. Your pyproject.toml specifies >=3.13, so this is fine, but it's worth noting for portability.

2. Magic Numbers

  • Line 130: 4096 RSA key size (good choice, but should be a named constant)
  • Line 130: 365 days validity (should be configurable or shorter)
  • Line 209: 5-second server start timeout (should be a constant)
  • Line 215: 120-second auth timeout (should be a constant)

Suggestion: Extract to class-level constants:

_SSL_KEY_SIZE = 4096
_SSL_CERT_DAYS = 30
_SERVER_START_TIMEOUT = 5
_AUTH_TIMEOUT = 120

3. Empty Lines

Lines 12-13: Three blank lines after imports (PEP 8 recommends two maximum)

4. HTML in Code

Lines 161-169: Inline HTML string could be extracted to a separate file or constant for maintainability.

🧪 Testing Concerns

Critical: No tests exist for this PR. Given the complexity of the new functionality, tests are essential.

Recommended test coverage:

  1. Mock server startup success/failure scenarios
  2. Test certificate generation with/without OpenSSL
  3. Test fallback to manual flow at each failure point
  4. Test URL parsing edge cases
  5. Test timeout scenarios
  6. Test with HTTP vs HTTPS redirect URIs
  7. Integration test with OAuth mock server

Test infrastructure needed:

  • Add pytest to dev dependencies
  • Mock webbrowser.open, subprocess.run, HTTPServer
  • Create fixtures for various OAuth callback responses

🎯 Performance Considerations

  1. Thread Cleanup: Daemon threads (line 205) will be killed abruptly. While acceptable here, consider threading.Timer or explicit cleanup for production code.

  2. Blocking Operations: The 120-second timeout (line 215) blocks the main thread. For a CLI tool this is fine, but document it.

  3. SSL Context Creation: Creating SSL context for each auth (lines 192-194) is fine since it's infrequent, but could be cached if auth happens frequently.

📝 Documentation Needs

  1. CLAUDE.md should be updated to mention:

    • Browser-based OAuth flow
    • Self-signed certificate generation
    • Fallback behavior
    • Port requirements (9090 or custom)
  2. Docstring improvements:

    • _generate_self_signed_cert(): Document OpenSSL dependency
    • _start_callback_server(): Document threading behavior and timeout
    • _perform_oauth_flow(): Document automatic vs manual flow
  3. User-facing documentation:

    • Firewall requirements for localhost:9090
    • Browser compatibility notes
    • What to do if automatic flow fails

🔧 Suggested Improvements

Priority 1 (Should fix before merge):

  1. Fix race condition in server thread
  2. Add error handling to fetch_token()
  3. Handle webbrowser.open() failure
  4. Move subprocess import to module level
  5. Extract magic numbers to constants

Priority 2 (Nice to have):

  1. Add basic test coverage
  2. Improve certificate management (expiry checking, key permissions)
  3. Try multiple ports before fallback
  4. Extract HTML to constant
  5. Add better URL parsing edge case handling

Priority 3 (Future enhancements):

  1. Make timeouts configurable
  2. Add progress indicator during wait
  3. Support custom certificate paths
  4. Add logging instead of print statements

🎉 Conclusion

This is a valuable UX improvement with solid fallback handling. The core concept is sound and follows industry best practices. However, there are several issues that should be addressed before merging:

Recommendation: Request changes to address Priority 1 items, especially the race condition and error handling. The lack of tests is concerning given the complexity, but could be addressed in a follow-up PR if needed.

Overall Assessment: Good implementation with room for improvement. The graceful degradation makes this safe to ship with the suggested fixes.


Review generated with assistance from Claude Code

- Enable HTTP for localhost OAuth via OAUTHLIB_INSECURE_TRANSPORT
- Change default redirect from https://localhost:9090/cb to http://localhost:8080
- Remove all HTTPS certificate generation code (openssl/mkcert)
- Force HTTP-only for clean, browser-friendly OAuth flow
- Fix timing issues: browser opens after server is ready
- Improve callback server architecture for better control
- Add enhanced success page with modern UI
- Support flexible port configuration via redirect URI

This eliminates browser security warnings and provides a smooth OAuth
experience. HTTP on localhost is secure per OAuth2 RFC 8252.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Oct 15, 2025

Pull Request Review: Add automatic browser-based OAuth flow

Overview

This PR adds a significant UX improvement by automating the OAuth flow with browser-based authentication and a local callback server. The implementation follows industry-standard patterns (similar to gcloud, aws-cli, gh). Overall, this is a well-thought-out enhancement with good fallback mechanisms.

✅ Strengths

  1. Excellent Fallback Strategy: Graceful degradation to manual flow when automatic flow fails
  2. Good Error Handling: Comprehensive exception handling for server binding failures and timeouts
  3. Clean Architecture: Well-structured code with clear separation of concerns
  4. User Experience: Automatic browser opening and nice success page significantly improve UX
  5. Thread Safety: Proper use of queue.Queue for thread-safe communication

🔴 Critical Issues

1. Security: Global Environment Variable Side Effect (oauth_session.py:14)

Setting os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' globally at module import time affects the entire Python process, not just this library. This could inadvertently enable insecure transport for other OAuth2 operations in the same process.

Risk: If this library is imported into a larger application, it could weaken security for other components.

Recommendation: Set this only within the relevant function scope, document this clearly as a security consideration, or only enable for localhost URIs.

2. Missing State Validation (oauth_session.py:268)

The state parameter is generated but never validated after the callback. OAuth2 best practices require validating the state parameter to prevent CSRF attacks.

Risk: Vulnerable to CSRF attacks during the OAuth flow.

Recommendation: Store the state and validate it when processing the callback.

3. No Request Timeout on Token Fetch (oauth_session.py:317-321)

Network request without timeout could hang indefinitely.

Recommendation: Add timeout parameter to fetch_token call.

⚠️ High Priority Issues

4. Resource Leak: Server Cleanup on Exception (oauth_session.py:251-254)

When an exception occurs, the error is re-raised without providing context. More importantly, if server_thread.start() fails after the server is created but before reaching the exception handler, the server might not be properly cleaned up.

5. Import Position (oauth_session.py:246)

Import statement (import time) in the middle of a function. Should be at module level.

6. Hard-coded Timeout Values

  • Server ready timeout: 5 seconds (line 240)
  • Callback timeout: 120 seconds (line 293)

Recommendation: Make these configurable via constructor parameters with sensible defaults.

📊 Test Coverage

Critical Issue: No tests exist for this functionality.

Recommendations:

  1. Add unit tests for _start_callback_server() with mocked server
  2. Test fallback scenarios (port in use, timeout, server errors)
  3. Test URL construction logic with various redirect URI formats
  4. Test state parameter validation (once implemented)
  5. Integration test for the full OAuth flow with a mock OAuth provider

🎯 Performance Considerations

  1. Sleep Call (line 247): The 0.1 second sleep is a code smell. Consider using proper synchronization instead.

  2. Single Request Handling: handle_request() only handles one request. If the browser makes multiple requests (favicon, etc.), subsequent requests will be ignored. This is acceptable for OAuth callbacks.

📋 Code Quality

Positive:

  • Clear docstrings
  • Good variable naming
  • Logical function decomposition
  • Comprehensive comments

Areas for Improvement:

  • Add type hints to nested BoundCallbackHandler class methods
  • Extract magic numbers to named constants
  • Consider extracting the HTML template to a separate constant or file
  • Add logging instead of print statements for better debugging

🔒 Security Summary

Issue Severity Status
Global OAUTHLIB_INSECURE_TRANSPORT High ⚠️ Needs fix
Missing state validation High ⚠️ Needs fix
HTTP allowed for non-localhost Medium ⚠️ Consider validation
No timeout on token fetch Medium ⚠️ Needs fix

📝 Recommendations Summary

Before Merge:

  1. ✅ Fix global environment variable side effect
  2. ✅ Add OAuth state validation
  3. ✅ Add timeout to fetch_token call
  4. ✅ Move import statement to module level
  5. ✅ Add basic tests for new functionality

Post-Merge:

  1. Make timeouts configurable
  2. Add comprehensive test suite
  3. Consider adding logging framework
  4. Add integration tests with mock OAuth provider

🎓 Minor Improvements

  1. Lines 282-283: Remove redundant message when falling back to manual flow
  2. Line 87: Consider allowing user to configure port via input
  3. PR Description: The mention of self-signed SSL certificates in the PR description doesn't match the implementation (uses HTTP, not HTTPS)

🏁 Verdict

Conditional Approval: This is a valuable feature with good implementation quality. However, the security issues (global environment variable and missing state validation) should be addressed before merging to production.

The code demonstrates good software engineering practices with fallback mechanisms and error handling. With the recommended security fixes and basic tests, this will be a solid addition to the codebase.


Great work on improving the user experience! The automatic browser flow is much more user-friendly than manual URL copy-pasting. 🚀

Review generated by Claude Code

Change default from http://localhost:8080 to http://localhost:8080/cb
for better OAuth convention alignment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Oct 15, 2025

Pull Request Review: Add automatic browser-based OAuth flow

Summary

This PR adds a significant UX improvement by implementing automatic browser-based OAuth flow with a local callback server. The implementation follows industry-standard patterns used by CLI tools. Overall, the code is well-structured with proper error handling and graceful fallbacks. However, there are several security concerns, potential bugs, and improvements to consider.


Critical Issues

1. Security: Global Environment Variable Modification (Line 14)

Problem: Setting OAUTHLIB_INSECURE_TRANSPORT globally at module import time affects all OAuth sessions in the process, not just this library. This could weaken security for other OAuth operations.

Impact: If this library is imported by a larger application, it inadvertently enables insecure transport for all OAuth operations across the entire process.

Recommendation:

  • Move this to be set contextually only when needed
  • Or make it configurable per-session
  • Add prominent security warnings in documentation about HTTP vs HTTPS

2. Security: HTTP Default for Redirect URI (Line 91)

Problem: Defaults to HTTP instead of HTTPS. While localhost traffic does not leave the machine, some OAuth providers reject HTTP redirect URIs, and it sets a poor security example.

Impact:

  • May fail with OAuth providers requiring HTTPS
  • Users might copy this pattern for non-localhost scenarios
  • Browser security policies increasingly restrict HTTP

Recommendation: Consider defaulting to HTTPS and implementing proper certificate handling as mentioned in the PR description.


Significant Issues

3. Resource Management: Server Cleanup on Early Return (Lines 279-284)

Problem: When returning None from _start_callback_server() at line 224, the server_ready event is never set, but we still return it. The calling code then checks both conditions, but the thread might still be running.

Recommendation: Ensure clean shutdown of any started threads before returning None.

4. Race Condition: Server Ready Check (Lines 239-243)

Problem: If the server takes slightly longer than 5 seconds to start (e.g., under heavy load), it is treated as a failure even though it might start successfully shortly after.

Impact: Unnecessary fallback to manual flow on slower systems.

Recommendation: Consider increasing timeout or making it configurable.

5. Error Handling: Missing State Validation (Line 268)

Problem: The state parameter is generated but never validated in the callback. OAuth2 state parameters are critical for CSRF protection.

Impact: Potential security vulnerability to CSRF attacks in the OAuth flow.

Recommendation: Store the state and validate it against the callback response.


Minor Issues & Improvements

6. Code Style: Inconsistent Spacing (Lines 16-17)

Issue: Three blank lines instead of the PEP 8 recommended two.

7. Import Organization (Line 246)

Issue: Import statement (import time) in the middle of a function. Should be at module level.
Recommendation: Move to top of file with other imports.

8. Error Handling: Generic Exception Catch (Line 251)

Issue: Catches all exceptions but does not log the error before re-raising.
Recommendation: Add logging or at least print the error for debugging.

9. Timeout Hardcoded (Line 293)

Issue: 2-minute timeout is hardcoded. Different users/scenarios might need different timeouts.
Recommendation: Make timeout configurable via constructor parameter.

10. Missing Error Context in Fallback (Lines 310-314)

Issue: User does not know how long they waited. Consider telling them Waited 2 minutes for clarity.


Test Coverage Concerns

The PR description includes a test plan, but no automated tests are included:

  • No unit tests for _start_callback_server()
  • No integration tests for the OAuth flow
  • No tests for fallback scenarios
  • No tests for error conditions

Recommendation: Add pytest tests covering:

  • Server startup success/failure
  • Callback reception
  • Timeout scenarios
  • Fallback to manual flow
  • Port already in use scenarios

Positive Aspects

  1. Excellent UX improvement - Eliminates copy/paste errors
  2. Graceful fallback - Always works even if automatic flow fails
  3. Good error handling - Multiple failure modes handled
  4. Clean code structure - Well-organized with clear separation of concerns
  5. Thread safety - Proper use of Queue for thread communication
  6. Resource cleanup - Server properly closed after use

Recommendations Summary

Must Fix:

  1. Address global OAUTHLIB_INSECURE_TRANSPORT security issue
  2. Implement OAuth state validation for CSRF protection
  3. Add proper resource cleanup for edge cases

Should Fix:

  1. Move inline import to module level
  2. Consider HTTPS default instead of HTTP
  3. Add automated tests
  4. Make timeout configurable

Nice to Have:

  1. Fix spacing/style issues
  2. Add error logging
  3. Document security considerations

Overall Assessment

This is a solid implementation that significantly improves the user experience. The code demonstrates good understanding of OAuth flows, threading, and error handling. However, the security concerns around the global environment variable and missing state validation need to be addressed before merging.

Recommendation: Request changes for security issues, then approve after fixes.

@justinpecott justinpecott requested a review from Copilot October 15, 2025 20:59
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements an automatic browser-based OAuth flow that improves the user experience by eliminating manual URL copy/pasting. The implementation includes automatic browser opening, a local HTTP server for callback handling, and graceful fallback to manual flow when needed.

Key Changes:

  • Adds automatic browser opening and local callback server for OAuth flow
  • Changes default redirect URI from HTTPS (port 9090) to HTTP (port 8080)
  • Implements timeout-based fallback to manual authorization flow

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


# Allow OAuth2 over HTTP for localhost (development only)
# This is safe because localhost traffic never leaves your machine
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting OAUTHLIB_INSECURE_TRANSPORT globally affects all OAuth sessions in the process. Consider setting this only for localhost redirect URIs to prevent accidentally allowing insecure transport for production OAuth endpoints. You can check if the redirect_uri is localhost before setting this environment variable.

Copilot uses AI. Check for mistakes.
def _prompt_for_config(self) -> Dict:
"""Prompt user for OAuth2 configuration and save it."""
print("No config saved to file yet, lets get set up!")
print("\nFor the redirect URL, use http://localhost:8080/cb (recommended for OAuth)")
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected capitalization of 'URL' in user-facing message.

Copilot uses AI. Check for mistakes.

def _start_callback_server(self, host: str, port: int) -> tuple[Optional[HTTPServer], queue.Queue, threading.Event]:
"""Start a local server to capture OAuth callback.

Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring doesn't document the host and port parameters. Add parameter descriptions to clarify their purpose and expected values.

Suggested change
Args:
host (str): Hostname or IP address for the local callback server to listen on (e.g., "localhost").
port (int): Port number for the local callback server (e.g., 8080).

Copilot uses AI. Check for mistakes.
Comment on lines +240 to +243
if not server_ready.wait(timeout=5):
print("Warning: Server failed to start, falling back to manual flow")
server.server_close()
return None, response_queue, server_ready
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If server_ready.wait() times out, the server thread may still be running. The thread is marked as daemon so it won't block process exit, but server.server_close() is called before the thread has confirmed startup. This could cause issues if the thread is still initializing. Consider adding a flag to signal the thread to stop or waiting briefly for the thread to complete.

Copilot uses AI. Check for mistakes.
Comment on lines +246 to +247
import time
time.sleep(0.1)
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The time module should be imported at the top of the file with other imports rather than inline within a function. This follows Python conventions and improves code readability.

Copilot uses AI. Check for mistakes.

# Wait for callback from queue (with timeout)
try:
callback_path = response_queue.get(timeout=120)
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timeout value of 120 seconds is a magic number. Consider defining it as a named constant (e.g., OAUTH_CALLBACK_TIMEOUT_SECONDS = 120) at the class or module level to improve maintainability and make it easier to adjust.

Copilot uses AI. Check for mistakes.
server, response_queue, server_ready = self._start_callback_server(host, port)

# Check if server started successfully
if server is None or not server_ready.is_set():
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition 'not server_ready.is_set()' is redundant here. If the server is None (line 224), server_ready.set() is never called, so it will never be set. The second condition will always be True when server is None. Consider simplifying to just 'if server is None:' for clarity.

Suggested change
if server is None or not server_ready.is_set():
if server is None:

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +308
redirect_response = urlunparse((
parsed_redirect.scheme,
parsed_redirect.netloc,
parsed_callback.path or parsed_redirect.path,
'', # params
parsed_callback.query,
'' # fragment
))
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for reconstructing the redirect URL could benefit from a comment explaining why we're combining components from both parsed_redirect and parsed_callback, particularly the fallback behavior for the path on line 304.

Copilot uses AI. Check for mistakes.
@justinpecott
Copy link
Owner Author

I think we're done here. This is just meant for utility scripts, not a production environment.

@justinpecott justinpecott merged commit 4ff6567 into main Oct 15, 2025
1 check passed
@justinpecott justinpecott deleted the auto-browser-oauth branch October 15, 2025 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant